/*
 * Copyright 2012 Claude Houle claude.houle@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.code.mochaccino.framework.bootstrap;

import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.integration.util.LogbackUtil;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.Stage;
import com.mycila.inject.jsr250.Jsr250;
import com.mycila.inject.jsr250.Jsr250Injector;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * This class has the responsability to bootstrap the application and keep the whole thing alive until
 * the SIGTERM is received.
 * <p/>
 * To see an example, @see com.google.code.mochaccino.framework.ui.demo.DemoServer
 * <p/>
 * For the Logging, the application will auto-detect logback.xml on the filesystem, if not present,
 * the fallback file will be used (packaged in the jar itself).
 */
public abstract class Bootstrap {

	/** Guice Injector */
	private Jsr250Injector injector = null;

	/** Keep Alive Lock */
	private Object keepAliveLock = new Object();

	/** Keep Alive? */
	private boolean keepAlive = true;

	/** Running */
	private AtomicBoolean running = new AtomicBoolean( false );

	/** Constructor */
	public Bootstrap( boolean keepAlive ) throws JoranException {
		LogbackUtil.configureLogback( "logback.xml", "default-logback.xml" );
		LogbackUtil.rerouteJavalUtilLogging();
		this.keepAlive = keepAlive;
	}

	/** Start the Application */
	public final void start( String[] args ) {
		interpretArgsBeforeInjection( args );
		injector = Jsr250.createInjector( Stage.PRODUCTION, getModules() );

		interpretArgsAfterInjection( args, injector );
		running.set( true );
		registerKeepAlive();
		registerShutdownHook();
	}

	/** Interpret Arguments */
	protected abstract void interpretArgsAfterInjection( String[] args, Injector injector );

	/** Interpret Arguments */
	protected abstract void interpretArgsBeforeInjection( String[] args );

	/** Provides the Guice Modules */
	protected abstract Module[] getModules();

	/** Register Keep Alive */
	private void registerKeepAlive() {
		if ( keepAlive ) {
			Thread keepAliveThread = new Thread( new Runnable() {
				@Override
				public void run() {
					while ( running.get() ) {
						try {
							synchronized (keepAliveLock) {
								keepAliveLock.wait( TimeUnit.MINUTES.toMillis( 1 ) );
							}
						} catch ( InterruptedException e ) {
							System.err.println( e.getMessage() );
							e.printStackTrace( System.err );
						}
					}
				}
			} );
			keepAliveThread.start();
		}
	}

	/** Register Shutdown Hook */
	private void registerShutdownHook() {
		Runtime.getRuntime().addShutdownHook( new Thread( new Runnable() {
			@Override
			public void run() {
				stop();
				synchronized (keepAliveLock) {
					keepAliveLock.notifyAll();
				}
			}
		} ) );
	}

	/** Stop the Application. Do cleanup with beans. */
	public final void stop() {
		doStop( injector );
		injector.destroy();
		injector = null;
		running.set( false );
	}

	/** Do Stop. This is called AFTER the injector.destroy() method is called */
	protected abstract void doStop( Injector injector );

	protected final void exit( int code, Throwable e ) {
		throw new RuntimeException( "System Exit with code " + code + ": " + e.getMessage(), e );
	}

	protected final void exit( int code, String e ) {
		throw new RuntimeException( "System Exit with code " + code + ": " + e );
	}
}
